﻿//#define _CRT_SECURE_NO_WARNINGS
//#include <stdio.h>
//#include <stdlib.h>
//#include <stddef.h>
#include "common.h"

// C/C++ 学习网站：https://legacy.cplusplus.com 注意旧版本（搜索）和新版本

/*
在 ANSI C 任何一种实现中，存在两个不同的环境
	1. 编译环境（翻译环境）：用于将源代码编译成目标代码的环境，包括编译器、链接器、库管理器等。
		编译环境包含了原文件经过编译器编译为可执行文件（.obj 后缀）和链接库
		一起由链接器生成可执行文件（.exe 后缀）
	2. 运行环境（执行环境）：用于运行目标代码的环境，包括操作系统、加载器、运行时库等。
编译分为三个过程：
	1. 预处理（Preprocessing）：将源代码中的预处理指令（#include、#define、#if、#endif等）
		处理掉，生成一个预处理后的源文件。（gcc 终端命令：gcc .c文件名 -E -o .i文件名）-o 表示输出文件
	2. 编译（Compilation）：将预处理后的源文件编译成目标代码，生成一个目标文件（.obj 后缀）。
		汇编中又包含反汇编等过程。
		gcc 编译终端命令：gcc .i文件名 -S 之后会生成一个同名的.s文件
		gcc 汇编终端命令：gcc .s文件名 -c 之后会生成一个同名的.o文件
	3. 链接（Linking）：将多个目标文件（.obj 后缀）和库文件（.lib 后缀）链接成
		一个可执行文件（.exe 后缀）。
		gcc 链接终端命令：gcc .o文件名 -o  可执行文件名.exe 之后会生成一个可执行文件
预处理阶段主要进行一些文本操作：
	1. 宏定义（#define）：将一个标识符替换为一个常数或其他标识符。
	2. 文件包含（#include）：将一个源文件的内容包含到另一个源文件中。
	3. 条件编译（#if、#else、#endif）：根据条件选择性地包含或排除代码。
	4. 行控制（#line）：用于标识当前行号。
	5. 错误和警告（#error、#warning）：用于产生编译错误或警告。
	6. 注释内容清除
	......
编译阶段主要进行词法分析、语法分析、语义分析、符号汇总、中间代码生成、代码优化、代码生成等过程。
	并生成汇编代码（.s 后缀文件）。
汇编阶段主要是将汇编代码转换为机器代码（二进制指令）、将符号汇总形成符号表；
	并生成目标文件（.obj 后缀文件）。
链接阶段主要进行库管理、符号决议、地址分配、合并段表、符号表、重定位（符号表）等过程。
	这里，没有意义的地址会被舍弃，并生成可执行文件（.exe 后缀文件）。
详细说明可以阅读《程序员的自我修养》、《深入理解计算机系统》等书籍。

预处理详解：
	预处理是指将源代码中的预处理指令（#include、#define、#if、#endif等）处理掉，
		生成一个预处理后的源文件。
	预处理的作用主要有：
	1. 宏定义：将一个标识符替换为一个常数或其他标识符。
	2. 文件包含：将一个源文件的内容包含到另一个源文件中。
	3. 条件编译：根据条件选择性地包含或排除代码。
	4. 行控制：用于标识当前行号。
	5. 错误和警告：用于产生编译错误或警告。
	6. 注释内容清除：将注释内容清除，使源代码更加易读。
	预处理的过程：
	1. 预处理指令的处理：预处理指令是以“#”开头的指令，预处理器会对其进行处理。
	2. 宏定义的展开：宏定义是指用一个标识符来代表一个常数或其他标识符，预处理器会将宏定义展开。
	3. 文件包含的处理：文件包含是指将一个源文件的内容包含到另一个源文件中，预处理器会将文件包含展开。
	4. 条件编译的处理：条件编译是指根据条件选择性地包含或排除代码，预处理器会根据条件判断是否包含代码。
	5. 行控制的处理：行控制是指用于标识当前行号，预处理器会将行号信息记录到输出文件中。
	6. 错误和警告的处理：错误和警告是指产生编译错误或警告，预处理器会将错误或警告信息记录到输出文件中。
	7. 注释内容清除的处理：注释内容清除是指将注释内容清除，使源代码更加易读，预处理器会将注释内容清除。
	预处理的输出文件：预处理后的源文件，通常以 “.i” 或 “.ii” 作为后缀。
		可以右键解决方案下的当前项目 -> 属性 -> C/C++ -> 预处理器 -> 预处理器 -> 预处理到文件
预定义符号：
	__FILE__：当前源文件名
	__LINE__：当前行号
	__DATE__：编译日期
	__TIME__：编译时间
	__STDC__：是否使用标准 C（1 表示使用，0 表示不使用）
		很遗憾，目前 GCC 编译器支持而 VS 编译器不支持，所以 VS 编译器无法使用 __STDC__ 预定义符号。
	__STDC_VERSION__：标准 C 的版本号
	__cplusplus：是否使用 C++

宏定义：
	宏定义是指用一个标识符来代表一个常数或其他标识符。
	宏定义的语法：#define 标识符 常数或其他标识符
		在需要换行时，可以使用 \ 符号。
	宏定义的作用：
		1. 代码重用：通过宏定义，可以将相同的代码片段用一个标识符代表，可以减少代码量。
		2. 类型定义：通过宏定义，可以定义一种新的类型，可以简化代码。
		3. 常量定义：通过宏定义，可以定义一种常量，可以简化代码。
		4. 条件编译：通过宏定义，可以根据条件选择性地包含或排除代码。
		5. 字符串替换：通过宏定义，可以实现字符串替换。
		6. 代码封装：通过宏定义，可以将代码封装到一个表达式中，可以隐藏实现细节。
		7. 代码优化：通过宏定义，可以对某些函数代码进行优化，可以提高运行效率。
		8. 代码生成：通过宏定义，可以生成代码，可以实现一些特殊功能。
	宏定义的注意事项：
		1. 宏定义的作用域：宏定义的作用域是全局的，在整个源文件中都有效。
		2. 宏定义的名称冲突：如果多个源文件中定义了相同的宏名，则会导致冲突。
		3. 宏定义的性能损失：宏定义会增加编译时间，应该尽量避免使用。
		4. 宏定义的调试困难：宏定义会使调试困难。
		5. 宏定义的可移植性差：宏定义会使代码不易移植。
		6. 宏定义的可读性差：宏定义会使代码不易读。
		7. 宏定义的命名空间污染：宏定义会污染命名空间。
		8. 宏定义的可维护性差：宏定义会使代码不易维护。
		9. 宏定义的可扩展性差：宏定义会使代码不易扩展。
		10. 宏定义的可重用性差：宏定义会使代码不易重用。
		11. 宏定义的可理解性差：宏定义会使代码不易理解。
		12. 宏定义可能会导致运算符优先级的问题。
		13. 因为宏的参数与类型无关，所以在宏和函数一起使用时，宏定义可能会导致语法错误。
	#define 机制包括了一个规定，允许把参数替换到文本中。
		列如：
			#define 名字(参数) 表达式
			其中，名字是宏名，参数是宏参数，可以使用逗号表达式，表达式是宏定义的表达式。
			当宏被调用时，预处理器会将参数替换到表达式中，并将结果作为宏定义的结果。
			例如：
				#define MAX(a,b) ((a)>(b)?(a):(b)) // 加括号是为了避免优先级问题
				#define ADD(a,b) ((a)+(b))
				#define SQR(a) (a*a)
				#define SQRT(a) (sqrt(a))
				#define LENGTH(str) (sizeof(str)/sizeof(str[0]))
				#define STR(x) #x
				#define STR4(x) STR(x)
				#define STR5(x) #x " is a " STR2(x)
				#define CAT(x, y) x##y
	#define 机制的实现：
		1. 预处理器扫描源代码，遇到 #define 指令时，会将其展开为相应的定义。
		2. 预处理器将展开后的结果输出到一个临时文件中。
		3. 编译器读取临时文件，并将其编译为目标代码。
		4. 编译器将目标代码输出到可执行文件。
	#define 机制的限制：
		1. 宏定义中不允许使用递归定义。
		2. 宏定义中不允许使用复杂的表达式。
		3. 宏定义中不允许使用宏参数。
		4. 宏定义中不允许使用函数调用。
		5. 宏定义中不允许使用运算符重载。
		6. 宏定义中不允许使用类型限定符。
		7. 宏定义中不允许使用宏名作为参数，但可以作为表达式。
		8. 宏定义中不允许使用预处理指令。
		9. 宏定义中不允许使用条件语句。
		10. 宏定义中不允许使用循环语句。
		11. 宏定义中不允许使用 goto 语句。
		12. 宏定义中不允许使用宏名作为变量名。
		13. 宏定义中不允许使用宏名作为函数名。
		14. 宏定义中不允许使用宏名作为结构成员名。
		15. 宏定义中不允许使用宏名作为枚举成员名。
		16. 宏定义中不允许使用宏名作为标签名。
		17. 宏定义中不允许使用宏名作为成员函数名。
		18. 宏定义中不允许使用宏名作为局部变量名。
		19. 宏定义中不允许使用宏名作为全局变量名。
		20. 宏定义中不允许使用宏名作为静态变量名。
		21. 宏定义中不允许使用宏名作为文件名。
		23. 宏定义中不允许使用宏名作为类型名。
		24. 宏定义中不允许使用宏名作为结构名。
		25. 宏定义中不允许使用宏名作为枚举名。
		27. 宏定义中不允许使用宏名作为结构成员名。
		28. 宏定义中不允许使用宏名作为枚举成员名。
		29. 宏定义中不允许使用宏名作为标签名。
		30. 宏定义中不允许使用宏名作为成员函数名。
		31. 宏定义中不允许使用宏名作为局部变量名。
		32. 宏定义中不允许使用宏名作为全局变量名。
		33. 宏定义中不允许使用宏名作为静态变量名。
		总之就是有很大的限制，使用起来比较不方便；但是宏有它不可替代的作用。
宏取消：
	宏定义的取消是指删除一个宏定义。
	宏定义的取消语法：#undef 宏名
	宏定义的取消作用：
		1. 防止宏定义的冲突：如果多个源文件中定义了相同的宏名，则可以通过宏定义的取消来解决冲突。
		2. 减少代码量：通过宏定义的取消，可以减少代码量。
		3. 优化代码：通过宏定义的取消，可以优化代码。
		4. 隐藏实现细节：通过宏定义的取消，可以隐藏实现细节。
		5. 实现代码封装：通过宏定义的取消，可以实现代码封装。
		6. 实现代码优化：通过宏定义的取消，可以实现代码优化。
		7. 实现代码生成：通过宏定义的取消，可以实现代码生成。
	宏定义的取消注意事项：
		1. 宏定义的取消不会影响到其他源文件。
		2. 宏定义的取消不会影响到同一个源文件中后续的宏定义。

命令行宏常量：
	在命令行中定义宏常量，可以在编译时指定宏常量的值。
	命令行宏常量的语法：-D 宏名[=值]    // 等号两边不能有空格
	命令行宏常量主要用于编译不同的版本

条件编译：
	条件编译是指根据条件选择性地包含或排除代码。
	条件编译的语法：
		#if 条件1
			程序块1（可以有嵌套的条件编译语句）
		#elif 条件2
			程序块2
		......
		#else
			不满足条件时的程序块
		#endif  // 结束条件编译，必须有
	条件编译的条件与 if ... else 语句类似。不同之处在于预处理阶段会根据条件判断是否包含代码。
	条件编译的作用：
		1. 隐藏实现细节：通过条件编译，可以隐藏实现细节。
		2. 优化代码重用、条件编译：通过条件编译，可以根据条件选择性地包含或排除代码。
		3. 实现代码封装：通过条件编译，可以实现代码封装。
		4. 跨平台编译：通过条件编译，可以实现跨平台编译，不同平台可以选择不同的代码。
	条件编译的过程：
		1. 预处理器扫描源代码，遇到 #if 指令时，会根据条件判断是否包含代码。
		2. 预处理器根据条件判断是否包含代码，并将结果输出到一个临时文件中，不包含的代码会被清除。
		3. 编译器读取临时文件，并将其编译为目标代码。
		4. 编译器将目标代码输出到可执行文件。

文件的包含：
	文件包含是指将一个源文件的内容包含到另一个源文件中。
	库文件包含的语法：#include <头文件名>
	自己的头文件包含的语法：#include "头文件名"
	区别在于：
		库文件包含：将系统提供的头文件包含到源文件中，系统提供的头文件一般位于 /usr/include 目录下。
		自己的头文件包含：将自己编写的头文件包含到源文件中，一般位于当前目录下。
		说明：
			头文件查找策略：
				编译器会优先在当前目录下查找，然后再去编译器库目录下查找，如果找不到，则会报错。
				安装 VS 时需要勾选 SDK 原因就是为了方便开发者使用系统提供的头文件。
			库文件包含使用 < > 系统提供的头文件一般是预编译好的，直接去编译器库目录下找，
				所以包含起来比较快；库文件的包含当然也可以使用 " " 但是这样包含查找起来比较慢（2次）。
			自己的头文件包含使用 " " 自己编写的头文件一般是当前目录下的文件，所以包含起来比较快。
*/

// 测试宏的使用
#define PI 3.14159265358979323846f
#define chr "预处理学习"
#define print printf("Hello, VS!\n")
#define regt register    // 定义寄存器（关键字）类型
#define b_case break;case    // 定义 break 和 case 联合语句（关键字），注意先 break，再 case
#define SQR(x) ((x) * (x))    // 定义宏 SQR(x)
#define ADD(x, y) ((x) + (y))    // 定义宏 ADD(x, y)
#define SQRADD(x, y) (SQR(x) + ADD(x, y))    // 宏包含其他宏，不能包含自身
#define print_char(x) printf("the char " \
							 #x " is %c\n", x)    // #x 代表参数 x 的字符名
#define print_value(a, fmt) printf("the value of " #a " is " fmt "\n", a)
#define cat_char(x, y) x ## y    // ## 连接两个参数名
#define max_value(x, y) ((x) > (y) ? (x) : (y))

// 宏实现的一些特殊功能测试
#define easy_malloc(num, type) (type*)malloc((num) * sizeof(type))  // 自定义 easy_malloc 宏

// 条件编译测试
#define __DUBEG__
#define TEST1
//#define TEST2
//#define TEST3

// 模仿 offsetof 宏的实现
#define my_offsetof(type, member) ((size_t)&(((type*)0)->member))  // 自定义 my_offsetof 宏
	// 说明：(type*)0 表示将 0 地址转换为 type 类型指针，然后再取它的 member 成员的地址偏移量。

int main(int argc, char* argv[]) // 主函数完整写法
{
	//测试宏的使用
	{
		print_start_line("测试宏的使用开始");
		print;
		printf("圆周率PI的值为：%f\n", PI);
		printf("the string is: %s\n", chr);
		int arr[5] = { 1, 2, 3, 4, 5 };
		int i;
		for (i = 0; i < 5; i++)
			printf("%s-%s打印%s文件%d行数组元素：%d\n",
				__DATE__, __TIME__, __FILE__, __LINE__, arr[i]);
		//printf("VS是否使用ANSI C标准：%d\n", __STDC__); // error: '__STDC__' undeclared
		printf("SQR(5 + 1) 的值为：%d\n", SQR(5 + 1));
		printf("20 + ADD(5 * 6, PI) 的值为：%.10f\n", 20 + ADD(5 * 6, PI)); // 宏的使用永远是替换
		printf("SQRADD(3, 4) 的值为：%d\n", SQRADD(3, 4));
		for (i = 'a'; i < 'g'; i++)
			print_char(i);
		char c1 = 'h';
		print_char(c1);
		print_value(c1, "%d");
		float f = 1.4142135623730950488016887242097f;
		print_value(f, "%.10f");
		char bt[] = "老变态了";
		printf("cat_char(b, t) 的结果为：%s\n", cat_char(b, t)); // 宏参数名连接
		int a = 2, b = 3;
		printf("max_value(a, b) 的结果为：%d\n", max_value(a++, b++));
		// 带副作用的传参，这种情况替换后较大值会被使用两次，注意这里是先使用后自增
		printf("a 的值为：%d, b 的值为：%d\n", a, b);
#undef print    // 取消宏 print
		//print;  // error: 'print' undeclared

		print_end_line("测试宏的使用结束");
	}

	// 宏实现的一些特殊功能测试
	{
		print_start_line("测试宏实现的一些特殊功能测试开始");
		int* pi1 = easy_malloc(5, int);
		int i;
		for (i = 0; i < 5; i++)
		{
			pi1[i] = i + 1;
			printf("pi1[%d] = %d\t", i, pi1[i]);
		}
		printf("\n");
		free(pi1);
		pi1 = NULL;

		print_end_line("测试宏实现的一些特殊功能测试结束");
	}

	// 条件编译测试
	{
		print_start_line("条件编译测试开始");
#if 1
		printf("条件编译测试：1\n");
#elif 0
		printf("条件编译测试：0\n");
#else
		printf("条件编译测试：else\n");
#endif // #if 1 宏定义的取消

#ifdef __DUBEG__  // 还可以写为 #if defined(__DUBEG__)
		printf("条件编译测试：__DUBEG__ 已定义\n");
#elif !defined(__DUBEG__)  // 没有定义时，进行预处理。
		// 这里不能写为 #elifndef __DUBEG__ 只有 #ifndef 没有 #elifndef
		printf("条件编译测试：__DUBEG__ 未定义\n");
#else
		printf("条件编译测试：__DUBEG__ 未定义\n");
#endif // #ifdef __DUBEG__

#ifdef TEST1 // TEST1 宏定义时，进行预处理。
#if defined(TEST2) && defined(TEST3)  // 三个宏都定义时，进行预处理。
		printf("条件编译测试：TEST1、TEST2 和 TEST3 都已定义\n");
#elif defined(TEST2)  // 只有两个宏TEST1 和 TEST2 都定义时，进行预处理。
		printf("条件编译测试：TEST1、TEST2 已定义，TEST3 未定义\n");
#elif defined(TEST3)  // 只有两个宏TEST1 和 TEST3 都定义时，进行预处理。
		printf("条件编译测试：TEST1、TEST3 已定义，TEST2 未定义\n");
#else  // 两个宏TEST2 和 TEST3 都未定义时，进行预处理。
		printf("条件编译测试：TEST1 已定义，TEST2 和 TEST3 都未定义\n");
#endif // #if defined(TEST2) && defined(TEST3)
#elif defined(TEST2)  // 只有 TEST2 宏定义、TEST1 宏未定义时，进行预处理。
#if defined(TEST3)  // 两个宏TEST1 和 TEST2 都定义时，进行预处理。
		printf("条件编译测试：TEST2 和 TEST3 已定义，TEST1 未定义\n");
#else  // 两个宏TEST2 和 TEST3 都未定义时，进行预处理。
		printf("条件编译测试：TEST2 已定义，TEST1 未定义，TEST3 未定义\n");
#endif // #if defined(TEST3)
#elif defined(TEST3)  // 只有 TEST3 宏定义、TEST1 和 TEST2 宏未定义时，进行预处理。
		printf("条件编译测试：TEST3 已定义，TEST1 和 TEST2 都未定义\n");
#else  // 三个宏都未定义时，进行预处理。
		printf("条件编译测试：三个 TEST 宏都未定义\n");
#endif // #ifdef TEST1

		print_end_line("条件编译测试结束");
	}

	// 模仿 offsetof 宏的实现
	{
		print_start_line("测试模仿 offsetof 宏的实现部分开始");
		typedef struct IntStruct {
			char a;
			int b;
			short c;
			double d;
		} A;
		// offsetof 宏用于计算偏移量
		printf("offsetof(struct A, a) 的值为：%zd\n", offsetof(A, a));
		printf("offsetof(struct A, b) 的值为：%zd\n", offsetof(A, b));
		printf("offsetof(struct A, c) 的值为：%zd\n", offsetof(A, c));
		printf("offsetof(struct A, d) 的值为：%zd\n", offsetof(A, d));

		// 模仿实现：my_offsetof 宏，长按 ctrl 点上面的 offsetof 可以看到
		// 这里的实现和 stddef.h 中的 offsetof 宏一目一样
		printf("my_offsetof(struct A, a) 的值为：%zd\n", my_offsetof(A, a));
		printf("my_offsetof(struct A, b) 的值为：%zd\n", my_offsetof(A, b));
		printf("my_offsetof(struct A, c) 的值为：%zd\n", my_offsetof(A, c));
		printf("my_offsetof(struct A, d) 的值为：%zd\n", my_offsetof(A, d));

		print_end_line("测试模仿 offsetof 宏的实现部分结束");
	}

	// 小作业部分一
	test_work_one();

	// 小作业部分二
	test_work_two();

	// 小作业部分三
	test_work_three();


	return EXIT_SUCCESS; // 0 表示成功（头文件为 <stdlib.h> ）
}
